/*
 * 描          述:  <描述>
 * 修  改   人:  PengQingyang
 * 修改时间:  2022年11月10日
 * <修改描述:>
 */
package com.tx.component.file.util;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Optional;

import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.tx.core.exceptions.util.AssertUtils;

import io.minio.BucketExistsArgs;
import io.minio.ListObjectsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveBucketArgs;
import io.minio.Result;
import io.minio.StatObjectArgs;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.http.HttpUtils;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import okhttp3.OkHttpClient;

/**
 * <功能简述>
 * <功能详细描述>
 * 
 * @author  PengQingyang
 * @version  [版本号, 2022年11月10日]
 * @see  [相关类/方法]
 * @since  [产品/模块版本]
 */
public class MinIOUtils {
    
    /** 默认的链接超时时间 */
    //参考： com.aliyun.oss.ClientConfiguration
    public static final int DEFAULT_CONNECTION_TIMEOUT = 50 * 1000;
    
    /**
     * 构建MinioClient
     * <功能详细描述>
     * @param endpoint
     * @param accessKey
     * @param secretKey
     * @return [参数说明]
     * 
     * @return MinioClient [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    public static MinioClient buildMinioClient(String endpoint,
            String accessKey, String secretKey) {
        return buildMinioClient(endpoint, accessKey, secretKey, null);
    }
    
    /**
     * 构建MinioClient
     * <功能详细描述>
     * @param endpoint
     * @param accessKey
     * @param secretKey
     * @return [参数说明]
     * 
     * @return MinioClient [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    public static MinioClient buildMinioClient(String endpoint,
            String accessKey, String secretKey, OkHttpClient httpClient) {
        if (httpClient == null) {
            httpClient = HttpUtils.newDefaultHttpClient(
                    DEFAULT_CONNECTION_TIMEOUT,
                    DEFAULT_CONNECTION_TIMEOUT,
                    DEFAULT_CONNECTION_TIMEOUT);
        }
        MinioClient minioClient = MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .httpClient(httpClient)
                .build();
        return minioClient;
    }
    
    /**
     * 判断桶是否存在<br/>
     *
     * @param minioClient
     * @param bucketName bucket名称
     * 
     * @return true存在，false不存在
     * 
     * @throws IOException 
     * @throws IllegalArgumentException 
     * @throws XmlParserException 
     * @throws ServerException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidResponseException 
     * @throws InternalException 
     * @throws InsufficientDataException 
     * @throws ErrorResponseException 
     * @throws InvalidKeyException 
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    public static boolean bucketExists(MinioClient minioClient,
            String bucketName)
            throws InvalidKeyException, ErrorResponseException,
            InsufficientDataException, InternalException,
            InvalidResponseException, NoSuchAlgorithmException, ServerException,
            XmlParserException, IllegalArgumentException, IOException {
        AssertUtils.notNull(minioClient, "minioClient is null.");
        AssertUtils.notEmpty(bucketName, "bucketName is empty.");
        return minioClient.bucketExists(
                BucketExistsArgs.builder().bucket(bucketName).build());
    }
    
    /**
     * 创建bucket
     * <功能详细描述>
     * @param minioClient
     * @param bucketName bucket名称
     * 
     * @return void [返回类型说明]
     * @throws IOException 
     * @throws IllegalArgumentException 
     * @throws XmlParserException 
     * @throws ServerException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidResponseException 
     * @throws InternalException 
     * @throws InsufficientDataException 
     * @throws ErrorResponseException 
     * @throws InvalidKeyException 
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    public static void createBucket(MinioClient minioClient, String bucketName)
            throws InvalidKeyException, ErrorResponseException,
            InsufficientDataException, InternalException,
            InvalidResponseException, NoSuchAlgorithmException, ServerException,
            XmlParserException, IllegalArgumentException, IOException {
        AssertUtils.notNull(minioClient, "minioClient is null.");
        AssertUtils.notEmpty(bucketName, "bucketName is empty.");
        
        if (!bucketExists(minioClient, bucketName)) {
            minioClient.makeBucket(
                    MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }
    
    /**
     * 根据bucketName获取信息
     *
     * @param minioClient
     * @param bucketName bucket名称
     * @return 单个桶信息
     * @throws IOException 
     * @throws XmlParserException 
     * @throws ServerException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidResponseException 
     * @throws InternalException 
     * @throws InsufficientDataException 
     * @throws ErrorResponseException 
     * @throws InvalidKeyException 
     */
    public static Optional<Bucket> getBucket(MinioClient minioClient,
            String bucketName)
            throws InvalidKeyException, ErrorResponseException,
            InsufficientDataException, InternalException,
            InvalidResponseException, NoSuchAlgorithmException, ServerException,
            XmlParserException, IOException {
        AssertUtils.notNull(minioClient, "minioClient is null.");
        AssertUtils.notEmpty(bucketName, "bucketName is empty.");
        
        return minioClient.listBuckets()
                .stream()
                .filter(b -> b.name().equals(bucketName))
                .findFirst();
    }
    
    /**
     * 根据bucketName删除信息
     *
     * @param bucketName bucket名称
     * @throws IOException 
     * @throws IllegalArgumentException 
     * @throws XmlParserException 
     * @throws ServerException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidResponseException 
     * @throws InternalException 
     * @throws InsufficientDataException 
     * @throws ErrorResponseException 
     * @throws InvalidKeyException 
     */
    public static void removeBucket(MinioClient minioClient, String bucketName)
            throws InvalidKeyException, ErrorResponseException,
            InsufficientDataException, InternalException,
            InvalidResponseException, NoSuchAlgorithmException, ServerException,
            XmlParserException, IllegalArgumentException, IOException {
        AssertUtils.notNull(minioClient, "minioClient is null.");
        AssertUtils.notEmpty(bucketName, "bucketName is empty.");
        
        minioClient.removeBucket(
                RemoveBucketArgs.builder().bucket(bucketName).build());
    }
    
    /**
     * 判断文件或文件夹是否存在
     * 
     * @param bucketName 桶名称
     * @param folderName 文件夹名称
     * @return true存在, 反之
     * @throws IOException 
     * @throws XmlParserException 
     * @throws ServerException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidResponseException 
     * @throws InternalException 
     * @throws InsufficientDataException 
     * @throws IllegalArgumentException 
     * @throws ErrorResponseException 
     * @throws InvalidKeyException 
     * @throws JsonParseException 
     * @throws JsonMappingException 
     */
    public static boolean exists(MinioClient minioClient, String bucketName,
            String relativePath) throws JsonMappingException,
            JsonParseException, InvalidKeyException, ErrorResponseException,
            IllegalArgumentException, InsufficientDataException,
            InternalException, InvalidResponseException,
            NoSuchAlgorithmException, ServerException, XmlParserException,
            IOException {
        AssertUtils.notNull(minioClient, "minioClient is null.");
        AssertUtils.notEmpty(bucketName, "bucketName is empty.");
        AssertUtils.notEmpty(relativePath, "relativePath is empty.");
        
        Iterable<Result<Item>> results = minioClient
                .listObjects(ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .prefix(relativePath)
                        .recursive(false)
                        .build());
        if (IterableUtils.isEmpty(results)) {
            return false;
        } else {
            for (Result<Item> result : results) {
                Item item = result.get();
                if (item.isDir()) {
                    if (StringUtils.equalsIgnoreCase(relativePath + "/",
                            item.objectName())) {
                        return true;
                    }
                } else {
                    if (StringUtils.equalsIgnoreCase(relativePath,
                            item.objectName())) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
    
    /**
     * 判断文件是否存在<br/>
     * @param minioClient
     * @param bucketName 桶名称
     * @param objectName 文件名称, 如果要带文件夹请用 / 分割, 例如 /help/index.html
     * @return true存在, 反之
     * @throws IOException 
     * @throws IllegalArgumentException 
     * @throws XmlParserException 
     * @throws ServerException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidResponseException 
     * @throws InternalException 
     * @throws InsufficientDataException 
     * @throws InvalidKeyException 
     * @throws ErrorResponseException 
     */
    public static boolean checkFileIsExist(MinioClient minioClient,
            String bucketName, String relativePath)
            throws InvalidKeyException, InsufficientDataException,
            InternalException, InvalidResponseException,
            NoSuchAlgorithmException, ServerException, XmlParserException,
            IllegalArgumentException, IOException, ErrorResponseException {
        AssertUtils.notNull(minioClient, "minioClient is null.");
        AssertUtils.notEmpty(bucketName, "bucketName is empty.");
        AssertUtils.notEmpty(relativePath, "relativePath is empty.");
        try {
            minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(relativePath)
                    .build());
            return true;
        } catch (ErrorResponseException ere) {
            if (StringUtils.equals("NoSuchKey", ere.errorResponse().code())) {
                return false;
            } else {
                throw ere;
            }
        }
    }
    
    /**
     * 判断文件夹是否存在
     *
     * @param bucketName 桶名称
     * @param folderName 文件夹名称
     * @return true存在, 反之
     * @throws IOException 
     * @throws XmlParserException 
     * @throws ServerException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidResponseException 
     * @throws InternalException 
     * @throws InsufficientDataException 
     * @throws IllegalArgumentException 
     * @throws ErrorResponseException 
     * @throws InvalidKeyException 
     * @throws JsonParseException 
     * @throws JsonMappingException 
     */
    public static boolean checkFolderIsExist(MinioClient minioClient,
            String bucketName, String relativePath) throws JsonMappingException,
            JsonParseException, InvalidKeyException, ErrorResponseException,
            IllegalArgumentException, InsufficientDataException,
            InternalException, InvalidResponseException,
            NoSuchAlgorithmException, ServerException, XmlParserException,
            IOException {
        AssertUtils.notNull(minioClient, "minioClient is null.");
        AssertUtils.notEmpty(bucketName, "bucketName is empty.");
        AssertUtils.notEmpty(relativePath, "relativePath is empty.");
        
        Iterable<Result<Item>> results = minioClient
                .listObjects(ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .prefix(relativePath)
                        .recursive(false)
                        .build());
        if (IterableUtils.isEmpty(results)) {
            return false;
        } else {
            for (Result<Item> result : results) {
                Item item = result.get();
                if (item.isDir()
                        && StringUtils.equalsIgnoreCase(relativePath + "/",
                                item.objectName())) {
                    return true;
                }
            }
            return false;
        }
    }
    
    /**
     * 上传MultipartFile文件到全局默认文件桶中
     * <功能详细描述>
     * @param file
     * @return [参数说明]
     * 
     * @return String [返回类型说明]
     * @throws IOException 
     * @throws IllegalArgumentException 
     * @throws XmlParserException 
     * @throws ServerException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidResponseException 
     * @throws InternalException 
     * @throws InsufficientDataException 
     * @throws ErrorResponseException 
     * @throws InvalidKeyException 
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    public static void putObject(MinioClient minioClient, String bucketName,
            String relativePath, MultipartFile file)
            throws InvalidKeyException, ErrorResponseException,
            InsufficientDataException, InternalException,
            InvalidResponseException, NoSuchAlgorithmException, ServerException,
            XmlParserException, IllegalArgumentException, IOException {
        AssertUtils.notNull(minioClient, "minioClient is null.");
        AssertUtils.notEmpty(bucketName, "bucketName is empty.");
        AssertUtils.notEmpty(relativePath, "relativePath is empty.");
        AssertUtils.notNull(file, "file is null.");
        
        try (InputStream stream = file.getInputStream();) {
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(relativePath)
                    .contentType(file.getContentType())
                    .stream(stream, stream.available(), -1)
                    .build());
        }
    }
    
    /**
     * 上传文件
     *
     * @param objectName  文件名
     * @param stream      文件流
     * @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 详细可看: https://www.runoob.com/http/http-content-type.html
     * 
     * @return 文件url
     * @throws IOException 
     * @throws IllegalArgumentException 
     * @throws XmlParserException 
     * @throws ServerException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidResponseException 
     * @throws InternalException 
     * @throws InsufficientDataException 
     * @throws ErrorResponseException 
     * @throws InvalidKeyException 
     */
    public void putObject(MinioClient minioClient, String bucketName,
            String relativePath, InputStream stream)
            throws InvalidKeyException, ErrorResponseException,
            InsufficientDataException, InternalException,
            InvalidResponseException, NoSuchAlgorithmException, ServerException,
            XmlParserException, IllegalArgumentException, IOException {
        AssertUtils.notNull(minioClient, "minioClient is null.");
        AssertUtils.notEmpty(bucketName, "bucketName is empty.");
        AssertUtils.notEmpty(relativePath, "relativePath is empty.");
        AssertUtils.notNull(stream, "stream is null.");
        
        // 给文件名添加时间戳防止重复
        String contentType = Files.probeContentType(Paths.get(relativePath));
        // 开始上传
        minioClient.putObject(PutObjectArgs.builder()
                .bucket(bucketName)
                .object(relativePath)
                .contentType(contentType)
                .stream(stream, stream.available(), -1)
                .build());
    }
    
    /**
     * 上传文件
     *
     * @param objectName  文件名
     * @param stream      文件流
     * @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 详细可看: https://www.runoob.com/http/http-content-type.html
     * 
     * @return 文件url
     * @throws IOException 
     * @throws IllegalArgumentException 
     * @throws XmlParserException 
     * @throws ServerException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidResponseException 
     * @throws InternalException 
     * @throws InsufficientDataException 
     * @throws ErrorResponseException 
     * @throws InvalidKeyException 
     */
    public void putObject(MinioClient minioClient, String bucketName,
            String relativePath, File file)
            throws InvalidKeyException, ErrorResponseException,
            InsufficientDataException, InternalException,
            InvalidResponseException, NoSuchAlgorithmException, ServerException,
            XmlParserException, IllegalArgumentException, IOException {
        AssertUtils.notNull(minioClient, "minioClient is null.");
        AssertUtils.notEmpty(bucketName, "bucketName is empty.");
        AssertUtils.notEmpty(relativePath, "relativePath is empty.");
        AssertUtils.notNull(file, "file is null.");
        AssertUtils.isTrue(file.exists(), "file is not exists.");
        
        // 给文件名添加时间戳防止重复
        String contentType = Files.probeContentType(Paths.get(relativePath));
        // 开始上传
        try (InputStream stream = new FileInputStream(file)) {
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(relativePath)
                    .contentType(contentType)
                    .stream(stream, stream.available(), -1)
                    .build());
        }
    }
    
    public void putObject(MinioClient minioClient, String bucketName,
            String relativePath, byte[] bytes)
            throws InvalidKeyException, ErrorResponseException,
            InsufficientDataException, InternalException,
            InvalidResponseException, NoSuchAlgorithmException, ServerException,
            XmlParserException, IllegalArgumentException, IOException {
        AssertUtils.notNull(minioClient, "minioClient is null.");
        AssertUtils.notEmpty(bucketName, "bucketName is empty.");
        AssertUtils.notEmpty(relativePath, "relativePath is empty.");
        AssertUtils.notNull(bytes, "bytes is null.");
        
        // 给文件名添加时间戳防止重复
        String contentType = Files.probeContentType(Paths.get(relativePath));
        // 开始上传
        try (InputStream stream = new ByteArrayInputStream(bytes)) {
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(relativePath)
                    .contentType(contentType)
                    .stream(stream, stream.available(), -1)
                    .build());
        }
    }
    
    //
    ///**
    // * 根据文件全路径获取文件流
    // *
    // * @param objectName 文件名称
    // * @return 文件流
    // */
    //public InputStream getObject(String objectName) {
    //    return this.getObject(minioProperties.getBucket(), objectName);
    //}
    //
    ///**
    // * 根据文件桶和文件全路径获取文件流
    // *
    // * @param bucketName 桶名称
    // * @param objectName 文件名
    // * @return 文件流
    // */
    //public InputStream getObject(String bucketName, String objectName) {
    //    try {
    //        return minioClient.getObject(GetObjectArgs.builder()
    //                .bucket(bucketName)
    //                .object(objectName)
    //                .build());
    //    } catch (Exception e) {
    //        throw new RuntimeException("根据文件名获取流失败!", e);
    //    }
    //}
    //
    ///**
    // * 根据url获取文件流
    // *
    // * @param url 文件URL
    // * @return 文件流
    // */
    //public InputStream getObjectByUrl(String url) {
    //    try {
    //        return new URL(url).openStream();
    //    } catch (IOException e) {
    //        throw new RuntimeException("根据URL获取流失败!", e);
    //    }
    //}
    //
    ///**
    // * 获取全部bucket
    // *
    // * @return 所有桶信息
    // */
    //public List<Bucket> getAllBuckets() {
    //    try {
    //        return minioClient.listBuckets();
    //    } catch (Exception e) {
    //        throw new RuntimeException("获取全部存储桶失败!", e);
    //    }
    //}
    //
    
    //
    ///**
    // * 删除文件
    // *
    // * @param objectName 文件名称
    // */
    //public boolean removeObject(String objectName) {
    //    try {
    //        this.removeObject(minioProperties.getBucket(), objectName);
    //    } catch (Exception e) {
    //        return false;
    //    }
    //    return true;
    //}
    //
    ///**
    // * 删除文件
    // *
    // * @param bucketName bucket名称
    // * @param objectName 文件名称
    // */
    //public boolean removeObject(String bucketName, String objectName) {
    //    try {
    //        minioClient.removeObject(RemoveObjectArgs.builder()
    //                .bucket(bucketName)
    //                .object(objectName)
    //                .build());
    //    } catch (Exception e) {
    //        return false;
    //    }
    //    return true;
    //}
    //
    ///**
    // * 生成唯一ID
    // *
    // * @param objectName 文件名
    // * @return 唯一ID
    // */
    //private static String getFileName(String objectName) {
    //    //判断文件最后一个点所在的位置
    //    int lastIndexOf = objectName.lastIndexOf(".");
    //    if (lastIndexOf == -1) {
    //        return String
    //                .format("%s_%s", objectName, System.currentTimeMillis());
    //    } else {
    //        // 获取文件前缀,已最后一个点进行分割
    //        String filePrefix = objectName.substring(0,
    //                objectName.lastIndexOf("."));
    //        // 获取文件后缀,已最后一个点进行分割
    //        String fileSuffix = objectName
    //                .substring(objectName.lastIndexOf(".") + 1);
    //        // 组成唯一文件名
    //        return String.format("%s_%s.%s",
    //                filePrefix,
    //                System.currentTimeMillis(),
    //                fileSuffix);
    //    }
    //}
    //
    ///**
    // * 获取文件信息, 如果抛出异常则说明文件不存在
    // *
    // * @param bucketName bucket名称
    // * @param objectName 文件名称
    // * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#statObject
    // */
    //public StatObjectResponse getObjectInfo(String bucketName,
    //        String objectName) throws Exception {
    //    return minioClient.statObject(StatObjectArgs.builder()
    //            .bucket(bucketName)
    //            .object(objectName)
    //            .build());
    //}
    //
    ///**
    // * 获取文件外链
    // *
    // * @param bucketName bucket名称
    // * @param objectName 文件名称
    // * @param expires    过期时间 <=7
    // * @return url
    // */
    //@SneakyThrows
    //public String getObjectURL(String bucketName, String objectName,
    //        Integer expires) {
    //    IntFunction<Integer> integerIntFunction = (int i) -> {
    //        return Math.min(i, 7);
    //    };
    //    return minioClient
    //            .getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
    //                    .bucket(bucketName)
    //                    .object(objectName)
    //                    .expiry(integerIntFunction.apply(expires))
    //                    .build());
    //}
    //
    ///**
    // * 根据文件前置查询文件
    // *
    // * @param bucketName bucket名称
    // * @param prefix     前缀
    // * @param recursive  是否递归查询
    // * @return MinioItem 列表
    // */
    //public List<Item> getAllObjectsByPrefix(String bucketName, String prefix,
    //        boolean recursive) {
    //    List<Item> list = new ArrayList<>();
    //    Iterable<Result<Item>> objectsIterator = minioClient
    //            .listObjects(ListObjectsArgs.builder()
    //                    .bucket(bucketName)
    //                    .prefix(prefix)
    //                    .recursive(recursive)
    //                    .build());
    //    if (objectsIterator != null) {
    //        for (Result<Item> result : objectsIterator) {
    //            Item item = result.get();
    //            list.add(item);
    //        }
    //    }
    //    return list;
    //}
    
    //public static void main(String[] args) throws IOException {
    //    String relativePath = "asdfa/asdfa/asdf/t.232342342342";
    //    String contentType = Files.probeContentType(Paths.get(relativePath));
    //    System.out.println(contentType == null);
    //    System.out.println(contentType);
    //}
}
